package com.lang.downloader.http;

import com.lang.Downloader;
import com.lang.downloader.http.LifeCycle.HttpDownloadLifeCycle;
import com.lang.downloader.http.config.CacheFile;
import com.lang.downloader.http.config.HttpDownloadConfig;
import com.lang.downloader.http.exception.HttpDownloadException;
import com.lang.downloader.http.header.HttpResponseHeader;
import com.lang.downloader.http.request.DefaultRequestConfig;
import com.lang.downloader.http.request.RequestConfig;
import com.lang.downloader.http.task.HttpDownloadTask;
import com.lang.downloader.http.util.RequestUtil;
import com.lang.progress.DProgress;
import com.lang.progress.HttpConfigDWhenCount;
import com.lang.progress.LogWhenCount;
import com.lang.util.DownloadConfigManager;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Http下载器
 */
@Slf4j
public class HttpDownloader implements Downloader {

    /**
     * 请求配置器
     */
    private final RequestConfig requestConfig;

    public HttpDownloader() {
        this(null);
    }

    /**
     * @param requestConfig 请求配置
     */
    public HttpDownloader(RequestConfig requestConfig) {
        this.requestConfig = ObjectUtils.defaultIfNull(requestConfig, new DefaultRequestConfig());
    }

    /**
     * 通过配置信息下载文件
     *
     * @param config 配置信息
     */
    public void download(HttpDownloadConfig config) {
        HttpDownloadLifeCycle downloadLifeCycle = new HttpDownloadLifeCycle();
        try {
            downloadLifeCycle.whenStart(config);
            OkHttpClient httpClient;
            //判断是否是断点续存，重新下载。 如果不是重启下载 那就需要设置需要下载的文件流
            if (!downloadLifeCycle.isRestart(config)) {
                httpClient = RequestUtil.okHttpClient(config);
                httpClient.newCall(requestConfig.requestBuilder().url(config.getUrl()).build()).execute();
                HttpResponseHeader responseHeader = config.getResponseHeader();
                String contentLengthStr = responseHeader.getContentLength();
                if (StringUtils.isBlank(contentLengthStr))
                    throw new HttpDownloadException(String.format("url=%s 没有文件大小(长度)", config.getUrl()));
                if (!NumberUtils.isNumber(contentLengthStr))
                    throw new HttpDownloadException(String.format("url=%s contentLength=%s不能转换为Long类型", config.getUrl(), contentLengthStr));
                Long contentLength = Long.valueOf(contentLengthStr);
                Long fileMaxSize = config.getFileMaxSize();
                config.setTempFiles(holdCacheFile(contentLength, config.getUrl(), config.getTempDir(), fileMaxSize));
                config.setContentLength(contentLength);
                DownloadConfigManager.writeDownloadConfig(config.getConfigFilePath(), config);
            } else {
                config = DownloadConfigManager.readConfig(config.getConfigFilePath(), HttpDownloadConfig.class);
            }
            //检查是否已下载完成
            if (downloadLifeCycle.checkTaskDone(config)) {
                log.info("文件={}已下载完成 url={}", Paths.get(config.getSavePath()).getFileName(), config.getUrl());
                return;
            } else {
                httpClient = RequestUtil.okHttpClient(config);
                ExecutorService executorService = Executors.newFixedThreadPool(config.getCorePoolSize());
                downloadLifeCycle.saveFileOutputStream(config);
                DProgress progress = new DProgress(Paths.get(config.getSavePath()).getFileName().toString(), config.getContentLength());
                AtomicReference<HttpDownloadConfig> configAtomicReference = new AtomicReference<>(config);
                Map<CacheFile, Call> fileCallMap = new ConcurrentHashMap<>();
                {
                    LogWhenCount logWhenCount = new LogWhenCount("Download");
                    HttpConfigDWhenCount httpConfigDWhenCount = new HttpConfigDWhenCount(configAtomicReference);
                    progress.addWhenCount(logWhenCount);
                    progress.addWhenCount(httpConfigDWhenCount);
                    for (CacheFile cacheFile : config.getTempFiles()) {
                        if (cacheFile.isDone()) {
                            progress.count(cacheFile.getLength());
                            continue;
                        }
                        fileCallMap.put(cacheFile, httpClient.newCall(requestConfig
                                .requestBuilder()
                                .addHeader("Range", String.format("bytes=%d-%d", cacheFile.getStart(), cacheFile.getEnd()))
                                .url(cacheFile.getHttpUrl())
                                .build()));
                    }
                }
                if (fileCallMap.isEmpty()) {
                    log.info("文件={} 已下载完成 url={}", Paths.get(config.getSavePath()).getFileName(), config.getUrl());
                    return;
                }
                Path tempDirPath = Paths.get(config.getTempDir());
                if (Files.notExists(tempDirPath) || !Files.isDirectory(tempDirPath)) {
                    Files.createDirectories(tempDirPath);
                }
                List<Runnable> tasks = new ArrayList<>();
                for (Map.Entry<CacheFile, Call> cacheFileCallEntry : fileCallMap.entrySet()) {
                    tasks.add(new HttpDownloadTask(config.getBufferSize(),
                            cacheFileCallEntry.getKey(),
                            cacheFileCallEntry.getValue(),
                            progress));
                }
                for (Runnable task : tasks) {
                    executorService.execute(task);
                }
                executorService.shutdown();
                Duration totalDownloadTime = config.getTotalDownloadTime();
                if (!executorService.awaitTermination(totalDownloadTime.toMillis(), TimeUnit.MILLISECONDS)) {
                    throw new HttpDownloadException(String.format("下载超时未完成 url=%s 文件=%s", config.getUrl(), config.getSavePath()));
                }
            }
            downloadLifeCycle.whenDone(config);
        } catch (Exception e) {
            downloadLifeCycle.whenException(config, e);
        } finally {
            downloadLifeCycle.whenFinish(config);
        }
    }

    /**
     * 下载文件
     *
     * @param url      链接
     * @param savePath 保存地址
     */
    @Override
    public void download(String url, String savePath) {
        download(new HttpDownloadConfig(url, savePath, null, null));
    }

    /**
     * 重启下载任务
     *
     * @param configFile 配置文件
     */
    @Override
    public void restart(String configFile) {
        download(DownloadConfigManager.readConfig(configFile, HttpDownloadConfig.class));
    }

    /**
     * @param contentLength 文件大小
     * @param url           下载链接
     * @param tempDir       临时文件路径
     * @param fileMaxSize   单链接下载最大文件
     * @return 把大文件流分成多个文件块
     */
    private List<CacheFile> holdCacheFile(Long contentLength, String url, String tempDir, Long fileMaxSize) {
        List<CacheFile> cacheFiles = new ArrayList<>();
        long size = contentLength / fileMaxSize;
        long leave = contentLength % fileMaxSize;
        for (int i = 0; i < size; i++) {
            CacheFile cacheFile = new CacheFile();
            cacheFile.setDone(false);
            cacheFile.setHttpUrl(url);
            cacheFile.setOrder(i);
            cacheFile.setFilePath(tempDir + File.separator + i + ".downloading");
            cacheFile.setStart(i * fileMaxSize);
            cacheFile.setEnd((i + 1) * fileMaxSize - 1);
            cacheFile.setLength(cacheFile.getEnd() - cacheFile.getStart());
            cacheFiles.add(cacheFile);
        }
        if (leave > 0) {
            CacheFile cacheFile = new CacheFile();
            cacheFile.setDone(false);
            cacheFile.setHttpUrl(url);
            cacheFile.setOrder((int) size);
            cacheFile.setFilePath(tempDir + File.separator + size + ".downloading");
            cacheFile.setStart(size * fileMaxSize);
            cacheFile.setEnd(contentLength);
            cacheFile.setLength(cacheFile.getEnd() - cacheFile.getStart());
            cacheFiles.add(cacheFile);
        }
        Path tempDirPath = Paths.get(tempDir);
        try {
            if (Files.notExists(tempDirPath) || !Files.isDirectory(tempDirPath))
                Files.createDirectories(tempDirPath);
        } catch (IOException e) {
            throw new HttpDownloadException("创建临时下载临时文件目录失败" + tempDir, e);
        }
        return cacheFiles;
    }
}
